/**
 * Copyright 2009-2010 - YangJiandong(chunquedong)
 * 
 * This file is part of ChunMap project
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE(Version >=3)
 * 你可以自由复制、传播本项目的下载包文件，但必须保持其完整性。
 * 我们不用对使用中的风险及由此造成的损失承担任何责任。
 * 详细情况请见《ChunMap许可协议》。

 * 想了解更多有关ChunMap的信息，请访问http://code.google.com/p/chunmap/
 */
package chunmap.model.geom;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import chunmap.model.coord.Transform;
import chunmap.model.elem.Envelope;
import chunmap.model.elem.PointLineBag;
import chunmap.model.relate.ComputeIm;
import chunmap.model.relate.IntersectionMatrix;
import chunmap.model.relate.relateop.Polygon_Polygon;

/**
 * @author chunquedong
 * 
 */
public class Polygon extends AbstractGeometry implements Iterable<Ring> {

	private final Ring shell;
	private final List<Ring> holes;

	/**
	 * @param shell
	 * @param holes
	 */
	public Polygon(Ring shell, List<Ring> holes) {
		if (shell == null) {
			throw new NullPointerException();
		}
		this.shell = shell;
		
		List<Ring> newHoles = new ArrayList<Ring>();
		if (holes != null) {
			for (Ring r : holes) {
				newHoles.add(r);
			}
		}
		this.holes = newHoles;
	}

	public Polygon(Ring shell) {
		this(shell, null);
	}

	public Ring getShell() {
		return shell;
	}

	public List<Ring> getHoles() {
		List<Ring> newHoles = new ArrayList<Ring>();
		for (Ring r : holes) {
			newHoles.add(r);
		}
		return newHoles;
	}

	public int ringCount() {
		return holes.size() + 1;
	}

	@Override
	protected Envelope calculateEnvelop() {
		return shell.getEnvelop();
	}

	@Override
	public GeometryType getGeometryType() {
		return GeometryType.Polygon;
	}

	@Override
	public String toString() {
		return "POLYGON" + toMyString();
	}

	public String toMyString() {
		StringBuilder str = new StringBuilder();
		str.append("(");
		str.append(shell.toMyString());
		for (Ring g : holes) {
			str.append("," + g.toMyString());
		}
		str.append(")");
		return str.toString();
	}

	@Override
	public Geometry getBoundary() {
		List<LineString> bs = new ArrayList<LineString>();
		bs.add(shell);
		bs.addAll(holes);

		return new MultiLineString(bs);
	}

	@Override
	protected boolean isSimple() {
		if (!shell.isValid())
			return false;
		for (Ring r : holes) {
			if (!r.isValid()) {
				return false;
			}
		}
		for (Ring r : holes) {
			if (!shell.containLineStringIn(r)) {
				return false;
			}
		}
		for (int i = 0, n = holes.size(); i < n; i++) {
			for (int j = i + 1; j < n; j++) {
				Ring r1 = holes.get(i);
				Ring r2 = holes.get(i);
				ComputeIm ac = new Polygon_Polygon(r1.toPolygon(), r2
						.toPolygon());
				if (ac.getIM().get(IntersectionMatrix.Inner,
						IntersectionMatrix.Inner) != IntersectionMatrix.EmptyDim) {
					return false;
				}
			}
		}
		return true;
	}

	@Override
	public GeometryCollection toGeometryCollection() {
		List<Polygon> gs = new ArrayList<Polygon>();
		gs.add(this);
		return new MultiPolygon(gs);
	}

	@Override
	public Geometry transform(Transform transf) {
		Ring lr = ((LineString) (shell.transform(transf))).toLinearRing();
		List<Ring> newHoles = new ArrayList<Ring>();
		for (Ring r : holes) {
			Ring newr = (Ring) (r.transform(transf));
			newHoles.add(newr);
		}
		return new Polygon(lr, newHoles);
	}

	public double computeArea() {
		double area = Math.abs(shell.computeArea());
		for (Ring r : holes) {
			double holesA = Math.abs(r.computeArea());
			area -= holesA;
		}
		return area;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((holes == null) ? 0 : holes.hashCode());
		result = prime * result + ((shell == null) ? 0 : shell.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Polygon other = (Polygon) obj;
		if (holes == null) {
			if (other.holes != null)
				return false;
		} else if (!holes.equals(other.holes))
			return false;
		if (shell == null) {
			if (other.shell != null)
				return false;
		} else if (!shell.equals(other.shell))
			return false;
		return true;
	}

	/**
	 * 是否完全落在孔中
	 * 
	 * @param line
	 * @return
	 */
	public boolean inHoles(LineString line) {
		// 打断
		List<LineString> lines = new ArrayList<LineString>();
		lines.add(line);
		for (Ring r : holes) {
			breakLine(r, lines);
		}

		for (LineString l : lines) {
			if (!this.inOneHoles(l)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * 完全落在一个孔里
	 * 
	 * @param line
	 * @return
	 */
	private boolean inOneHoles(LineString line) {
		for (Ring r : holes) {
			if (r.containLineStringIn(line)) {
				return true;
			}
		}
		return false;
	}

	private void breakLine(Ring r, List<LineString> lines) {
		for (int i = 0, n = lines.size(); i < n; i++) {
			LineString line = lines.get(i);
			PointLineBag bag = line.intersection(r);
			List<LineString> newLines = bag.breakLine(line);
			
			if (newLines.size() > 1) {
				lines.remove(i);
				lines.addAll(newLines);
				return;
			}
		}
	}

	@Override
	public Iterator<Ring> iterator() {
		return new Iterator<Ring>() {
			Iterator<Ring> it = holes.iterator();
			int i = 0;

			@Override
			public boolean hasNext() {
				if (i == 0)
					return true;
				else {
					return it.hasNext();
				}
			}

			@Override
			public Ring next() {
				if (i == 0)
					return shell;
				else
					return it.next();
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException();
			}
		};
	}
}
